﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Lab2___Mineria_de_datos.Modelo;

namespace Lab2___Mineria_de_datos.Main
{
    /// <summary>
    /// Clase principal encargada de producir un árbol con todas las combinaciones que cumplan con los parámetros de entrada.
    /// </summary>
    public class ArbolDeCombinatorias : IDisposable
    {
        private Entities db;
        private int id_arbol_actual = 1;

        private Restricciones res;
        private float soporte_minimo;
        private int numero_max_de_combinaciones;

        /// <summary>
        /// Constructor:
        ///     - Inicia las variables necesarias para el trabajo.
        ///     - Inicia el nivel del árbol de combinatorias a 0 (raiz del árbol).
        /// </summary>
        /// <param name="s_m">Soporte mínimo ingresado como parámetro en la GUI</param>
        public ArbolDeCombinatorias(float s_m, int max_combis)
        {
            db = new Entities();

            soporte_minimo = s_m;
            numero_max_de_combinaciones = max_combis;
            res = new Restricciones(db);//, soporte_minimo);
            
            db.NIVEL_ARBOL.AddObject(new NIVEL_ARBOL
            {
                NUMERO_NIVEL = 0,
                ES_ULTIMO = true
            });

            db.SaveChanges();
        }

        /// <summary>
        /// Procedimiento principal que crea las combinaciones mediante un árbol.
        /// El árbol se va podando en la medida que una rama siguiente no cumpla con los parámetros de entrada.
        /// O(n) = A*R*O(Soporte)*O(Confianza) | A := número de atributos ; R := número de rangos de cada atributo ;
        ///                                    | O(Soporte) := orden de complejidad del Soporte de la combinatoria ;
        ///                                    | O(Confianza) := orden de complejidad de la confianza de la combinatoria
        /// </summary>
        public void Go()
        {
            if (NivelActual().NUMERO_NIVEL == 0)
            {
                NIVEL_ARBOL nivel_inferior = BajaDeNivel();
                foreach (ATRIBUTO atributo in db.ATRIBUTO)
                {
                    foreach (RANGO rango in atributo.RANGO)
                    {
                        ARBOL arbol = new ARBOL
                        {
                            ID_RAIZ = id_arbol_actual,
                            ATRIBUTO = atributo,
                            ID_PADRE = null,
                            CORTADO = false,
                            RANGO = rango,
                            NIVEL_ARBOL = nivel_inferior
                        };

                        db.ARBOL.AddObject(arbol);
                        db.SaveChanges();

                        id_arbol_actual++;
                    }
                }

                Go();
            }
            else
            {
                NIVEL_ARBOL nivel_actual = NivelActual();
                NIVEL_ARBOL siguiente_nivel = new NIVEL_ARBOL
                {
                    NUMERO_NIVEL = nivel_actual.NUMERO_NIVEL + 1,
                    ES_ULTIMO = false
                };

                db.NIVEL_ARBOL.AddObject(siguiente_nivel);
                db.SaveChanges();

                int no_cortados = 0;
                foreach (ARBOL arbol in nivel_actual.ARBOL)
                {
                    if (arbol.CORTADO)
                    {
                        continue;
                    }

                    foreach (ATRIBUTO sig_atrib in SiguienteCombinacion(arbol))
                    {
                        foreach (RANGO sig_rango in sig_atrib.RANGO)
                        {
                            ARBOL nuevo_arbol = new ARBOL
                            {
                                ID_RAIZ = id_arbol_actual,
                                ATRIBUTO = sig_atrib,
                                //ARBOL1 = this,
                                CORTADO = res.Soporte(TrazaParaProximo(arbol, sig_rango)) < soporte_minimo,
                                //ID_PADRE = arbol.ID_RAIZ,
                                ARBOL2 = arbol, //PADRE
                                RANGO = sig_rango,
                                NIVEL_ARBOL = siguiente_nivel
                            };

                            db.ARBOL.AddObject(nuevo_arbol);
                            db.SaveChanges();
                            no_cortados++;
                            id_arbol_actual++;
                        }
                    }
                }

                nivel_actual.ES_ULTIMO = false;
                siguiente_nivel.ES_ULTIMO = true;
                db.DetectChanges();
                db.SaveChanges();

                if (no_cortados == 0)
                {
                    throw new NoMasCombinaciones();
                }
                else
                {
                    Go();
                }
            }
        }

        #region Helpers

        private IEnumerable<ATRIBUTO> TrazaYaAgregados(ARBOL arbol)
        {
            for (var aux = arbol; aux != null; aux = aux.ARBOL2)
            {
                yield return aux.ATRIBUTO;
            }
        }

        /// <summary>
        /// Determina la siguiente combinatoria (con todos sus elementos), para ser evaluada.
        /// O(n) = P | P := profundidad del árbol
        /// </summary>
        /// <param name="arbol">Combinatoria actual de la rama</param>
        /// <param name="proximo">Siguiente elemento en la combinatoria</param>
        /// <returns>Traza completa de la rama actual más el siguiente elemento que corresponde a la combinatoria</returns>
        private RANGO[] TrazaParaProximo(ARBOL arbol, RANGO proximo)
        {
            List<RANGO> rangos = new List<RANGO>();
            for (var aux = arbol; aux != null; aux = aux.ARBOL2)
            {
                rangos.Add(aux.RANGO);
            }

            rangos.Add(proximo);
            return rangos.ToArray();
        }

        /// <summary>
        /// Determina cual es el siguiente Atributo que sigue en la combinatoria.
        /// Los Rangos de este atributo servirán para crear los nuevo árboles.
        /// O(n) = A^2 | A := número de atributos del problema
        /// </summary>
        /// <param name="combinacion">Árbol para el cual se determina su siguiente combinación</param>
        /// <returns>La siguiente combinación</returns>
        private IEnumerable<ATRIBUTO> SiguienteCombinacion(ARBOL combinacion)
        {
            foreach (ATRIBUTO todos_los_atrib in ListaDeAtributos())
            {
                foreach (ATRIBUTO atributo_en_el_arbol in TrazaYaAgregados(combinacion).ToArray().Reverse())
                {
                    if (atributo_en_el_arbol.Equals(todos_los_atrib))
                    {
                        break;
                    }
                    else
                    {
                        yield return todos_los_atrib;
                    }
                }
            }

            //yield return null;
        }

        /// <summary>
        /// O(n) = A | A:= número de atributos
        /// </summary>
        /// <returns>Lista con todos los atributos</returns>
        private ATRIBUTO[] ListaDeAtributos()
        {
            return db.ATRIBUTO.OrderBy(a => a.ID_ATRIBUTO).ToArray();
        }

        /// <summary>
        /// Determina el nivel actual con el cual se está trabajando.
        /// O(n) = N | N := número de niveles del árbol
        /// </summary>
        /// <returns>Nivel actual del árbol</returns>
        private NIVEL_ARBOL NivelActual()
        {
            return db.NIVEL_ARBOL.First(n => n.ES_ULTIMO);
        }

        /// <summary>
        /// Aumenta en uno el nivel del árbol, cambiando el nivel actual.
        /// O(n) = N | N := número de niveles del árbol
        /// </summary>
        /// <returns>El nuevo nivel</returns>
        private NIVEL_ARBOL BajaDeNivel()
        {
            NIVEL_ARBOL anterior = NivelActual();
            anterior.ES_ULTIMO = false;
            NIVEL_ARBOL actual = new NIVEL_ARBOL
            {
                NUMERO_NIVEL = anterior.NUMERO_NIVEL + 1,
                ES_ULTIMO = true
            };
            db.NIVEL_ARBOL.AddObject(actual);
            db.DetectChanges();
            db.SaveChanges();

            return actual;
        }

        #endregion

        public void Dispose()
        {
            if (db != null)
            {
                db.Dispose();
            }

        }

        //public class NodoArbol
        //{
        //    public RANGO ValorNodo { get; set; }
        //    public NodoArbol Padre { get; set; }
        //    public bool Cortado { get; set; }

        //    private List<NodoArbol> Hijos;

        //    //public NodoArbol(NodoArbol padre) : this(padre, null) { }

        //    public NodoArbol (NodoArbol padre, RANGO valor)
        //    {
        //        this.Padre = padre;
        //        this.ValorNodo = valor;
        //        this.Cortado = false;
        //        Hijos = new List<NodoArbol>();
        //    }

        //    public void AddHijo(NodoArbol hijo)
        //    {
        //        Hijos.Add(hijo);
        //    }

        //    public NodoArbol[] GetHijos()
        //    {
        //        return Hijos.ToArray();
        //    }

        //    public IEnumerable<NodoArbol> TrazaYaAgregados()
        //    {
        //        for (var aux = this; aux != null; aux = aux.Padre)
        //        {
        //            yield return aux;
        //        }
        //    }


        //    //public bool EstaHijo()
        //    //{
        //    //    return 
        //    //}
        //}

        //foreach (ATRIBUTO sig_atrib in SiguienteCombinacion(arbol))
        //{
        //    foreach (RANGO sig_rango in sig_atrib.RANGO)
        //    {
        //        ARBOL nuevo_arbol = new ARBOL
        //        {
        //            ID_RAIZ = id_arbol_actual,
        //            ATRIBUTO = sig_atrib,
        //            CORTADO = res.Soporte(TrazaParaProximo(arbol, sig_rango)) < soporte_minimo,
        //            ID_PADRE = arbol.ID_RAIZ,
        //            RANGO = sig_rango,
        //            NIVEL_ARBOL = siguiente_nivel
        //        };

        //        db.ARBOL.AddObject(nuevo_arbol);
        //        db.SaveChanges();
        //    }

        //    id_arbol_actual++;
        //    no_cortados++;
        //}
    }
}
